In this project, we’ll build a Recipe Finder Web App using HTML, CSS (Bootstrap), and JavaScript. The app lets users search for recipes by name and view details like ingredients, instructions, and a YouTube video. It uses the TheMealDB API to fetch real recipe data dynamically. The search bar allows instant lookups, and results are displayed in clean, responsive Bootstrap cards. By making this project, you’ll learn how to work with APIs, handle user input, and dynamically update web content with JavaScript.
🧩 Project Overview
The main idea of this project is simple:
Built a Recipe Finder Web App using HTML, CSS (Bootstrap), and JavaScript.
Integrated TheMealDB API to fetch and display real-time recipe data.
Added a search bar to find recipes instantly by name.
Displayed results in responsive Bootstrap cards with images and categories.
Used a modal window to show detailed recipe information, ingredients, and YouTube tutorials.
Demonstrates practical use of API integration, DOM manipulation, and Bootstrap UI design.
Navbar and Search Input
The navigation bar is built using Bootstrap and contains a brand title with a search input field. Users can type a recipe name here to search. The form does not have a submit button — instead, pressing the Enter key triggers the search function.
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Recipe Finder</a>
<form id="searchForm" class="d-flex w-100 justify-content-end" role="search">
<input id="searchInput" class="form-control search-input" type="search" placeholder="Search recipes...">
</form>
</div>
</nav>
Fetching Recipes from TheMealDB API
This function takes the user’s search query and sends a request to the TheMealDB API. It then converts the JSON response into usable JavaScript data and calls another function to display the results.
async function getMeals(query) {
if (!query) {
message.textContent = "Please type a recipe name.";
mealsDiv.innerHTML = "";
return;
}
message.textContent = "Loading recipes...";
const res = await fetch(https://www.themealdb.com/api/json/v1/1/search.php?s=${query});
const data = await res.json();
if (!data.meals) {
message.textContent = "No recipes found.";
mealsDiv.innerHTML = "";
return;
}
message.textContent = Showing ${data.meals.length} recipe(s);
displayMeals(data.meals);
}
Displaying Recipe Cards
The displayMeals() function dynamically creates Bootstrap cards for each recipe returned by the API. Each card shows the meal image, name, category, and area, along with a button to view full details.
function displayMeals(meals) {
mealsDiv.innerHTML = meals.map(meal => `
<div class="col-12 col-md-4">
<div class="card h-100 shadow-sm">
<img src="${meal.strMealThumb}" class="card-img-top" alt="${meal.strMeal}">
<div class="card-body d-flex flex-column">
<h5 class="card-title">${meal.strMeal}</h5>
<p class="text-muted small">${meal.strCategory} | ${meal.strArea}</p>
<button class="btn btn-primary mt-auto" onclick="showDetails(${meal.idMeal})">
View Recipe
</button>
</div>
</div>
</div>
`).join("");
}
Showing Recipe Details in Modal
When a user clicks the “View Recipe” button, the showDetails() function fetches more details for that meal using its unique ID. It displays the ingredients, instructions, and a YouTube video link inside a Bootstrap modal.
async function showDetails(mealId) {
const res = await fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${mealId}`);
const data = await res.json();
const meal = data.meals[0];
let ingredients = "";
for (let i = 1; i <= 20; i++) {
const ing = meal[`strIngredient${i}`];
const measure = meal[`strMeasure${i}`];
if (ing) ingredients += "<li>" + ing + " - " + measure + "</li>";
}
modalBody.innerHTML =
"<div class='text-center mb-3'>" +
"<img src='" + meal.strMealThumb + "' class='img-fluid rounded' style='max-height:250px'>" +
"</div>" +
"<h5>" + meal.strMeal + "</h5>" +
"<p><strong>Category:</strong> " + meal.strCategory + "</p>" +
"<p><strong>Area:</strong> " + meal.strArea + "</p>" +
"<h6>Ingredients:</h6>" +
"<ul>" + ingredients + "</ul>" +
"<h6>Instructions:</h6>" +
"<p>" + meal.strInstructions + "</p>" +
(meal.strYoutube
? "<a href='" + meal.strYoutube + "' target='_blank' class='btn btn-danger'>Watch Video</a>"
: ""
);
recipeModal.show();
}
Search Input Event Listener
This event listener waits for the user to press the Enter key inside the search box. When pressed, it calls the getMeals() function with the user’s input to perform a new search.
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
getMeals(searchInput.value.trim());
}
});
Default Recipe on Page Load
When the page first loads, this line automatically searches for “Arrabiata” so the user sees an example recipe right away.
window.addEventListener("load", () => getMeals("Arrabiata"));
Full Code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Recipe Finder</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body{
background-color: white;
}
.card img{
height: 200px; object-fit: cover;
}
.search-input{
width: 400px;
}
@media (max-width: 768px){
.search-input { width: 100%; } }
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="#">Recipe Finder</a>
<form id="searchForm" class="d-flex w-100 justify-content-end" role="search">
<input id="searchInput" class="form-control search-input" type="search" placeholder="Search recipes...">
</form>
</div>
</nav>
<div class="container my-4">
<div id="message" class="text-center text-muted mb-3"></div>
<div id="meals" class="row g-3"></div>
</div>
<div class="modal fade" id="recipeModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Recipe Details</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="modalBody"></div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script>
const searchInput = document.getElementById("searchInput");
const mealsDiv = document.getElementById("meals");
const message = document.getElementById("message");
const modalBody = document.getElementById("modalBody");
const recipeModal = new bootstrap.Modal(document.getElementById("recipeModal"));
async function getMeals(query) {
if (!query) {
message.textContent = "Please type a recipe name.";
mealsDiv.innerHTML = "";
return;
}
message.textContent = "Loading recipes...";
const res = await fetch(`https://www.themealdb.com/api/json/v1/1/search.php?s=${query}`);
const data = await res.json();
if (!data.meals) {
message.textContent = "No recipes found.";
mealsDiv.innerHTML = "";
return;
}
message.textContent = `Showing ${data.meals.length} recipe(s)`;
displayMeals(data.meals);
}
function displayMeals(meals) {
mealsDiv.innerHTML = meals.map(meal => `
<div class="col-12 col-md-4">
<div class="card h-100 shadow-sm">
<img src="${meal.strMealThumb}" class="card-img-top" alt="${meal.strMeal}">
<div class="card-body d-flex flex-column">
<h5 class="card-title">${meal.strMeal}</h5>
<p class="text-muted small">${meal.strCategory} | ${meal.strArea}</p>
<button class="btn btn-primary mt-auto" onclick="showDetails(${meal.idMeal})">
View Recipe
</button>
</div>
</div>
</div>
`).join("");
}
async function showDetails(mealId) {
const res = await fetch(`https://www.themealdb.com/api/json/v1/1/lookup.php?i=${mealId}`);
const data = await res.json();
const meal = data.meals[0];
let ingredients = "";
for (let i = 1; i <= 20; i++) {
const ing = meal[`strIngredient${i}`];
const measure = meal[`strMeasure${i}`];
if (ing) ingredients += `<li>${ing} - ${measure}</li>`;
}
modalBody.innerHTML = `
<div class="text-center mb-3">
<img src="${meal.strMealThumb}" class="img-fluid rounded" style="max-height:250px">
</div>
<h5>${meal.strMeal}</h5>
<p><strong>Category:</strong> ${meal.strCategory}</p>
<p><strong>Area:</strong> ${meal.strArea}</p>
<h6>Ingredients:</h6>
<ul>${ingredients}</ul>
<h6>Instructions:</h6>
<p>${meal.strInstructions}</p>
${meal.strYoutube ? `<a href="${meal.strYoutube}" target="_blank" class="btn btn-danger"><i class="bi bi-youtube"></i> Watch Video</a>` : ""}
`;
recipeModal.show();
}
searchInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
getMeals(searchInput.value.trim());
}
});
</script>
</body>
</html>